-
Notifications
You must be signed in to change notification settings - Fork 2.1k
BlockStoreProtocol #19799
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
BlockStoreProtocol #19799
Conversation
Pull Request Test Coverage Report for Build 16382258510Details
💛 - Coveralls |
806414d
to
208c082
Compare
7cd180b
to
1312c4f
Compare
|
||
async def check_block_store_invariant(bc: Blockchain): | ||
db_wrapper = bc.block_store.db_wrapper | ||
block_store = cast(BlockStore, bc.block_store) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is ugly for sure, but we can get away with it because we know that bc
has been populated with a BlockStore
instance here.
In future PRs, I expect I will have to rework some tests pretty significantly.
in_chain = set() | ||
max_height = -1 | ||
async with db_wrapper.writer_maybe_transaction() as conn: | ||
async with block_store.transaction() as conn: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The BlockStoreProtocol
now has a .transaction
generic async context manager which for BlockStore
does the same thing as the prior code.
await _validate_and_add_block(b, block) | ||
|
||
blocks = await b.get_block_records_at(heights, batch_size=2) | ||
blocks = await b.get_block_records_at(heights) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The batching should be handled by the BlockStoreProtocol
instance. I've pushed it into BlockStore
, so get_block_records_at
no longer accepts batch_size
(which is how it really should work, right?).
try: | ||
# Always add the block to the database | ||
async with self.block_store.db_wrapper.writer(): | ||
async with self.block_store.transaction(): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't want to require that our BlockStoreProtocol
instance have a very sqlite3-specific .db_wrapper
(since Rocks DB won't).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we currently rely on BlockStore
and CoinStore
to use the same database and that updates to them are part of the same transaction. It's not great that this dependency is kind of implied in many ways. But I think it's important to preserve. If we extend the chain by one block in BlockStore
and that transaction commits, the CoinStore
better have the new coins committed too, as well as the state of the coin set (which is currently implied by the coin table).
This looks like a step away from this, and it's not clear to me that we can step away from it without some other mechanism to keep them in sync.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't worry, I have this issue firmly in my sights. As we discussed elsewhere, DBWrapper2
is a problem because it requires all stores to have the same DB, which may not continue to be true going forward. But I am aware of why transactional integrity is required and am keeping it in mind. A two-phase commit with resolution of conflicts upon DB open is in our future (if necessary).
blocks: list[FullBlock] = [] | ||
for hash in hashes.copy(): | ||
block = self.block_store.block_cache.get(hash) | ||
block = self.block_store.get_block_from_cache(hash) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't want to require our BlockStoreProtocol
to have a block_cache
. Ultimately, we should consider unifying the block cache and BlockStoreProtocol
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah, I agree. I experimented with making the cache transparent once, and there are a lot of functions that would have to turn async
and probably slow down a lot as a result
return header_dict[header_hash] | ||
|
||
async def get_block_records_at(self, heights: list[uint32], batch_size: int = 900) -> list[BlockRecord]: | ||
async def get_block_records_at(self, heights: list[uint32]) -> list[BlockRecord]: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't actually want a batch_size
parameter here: it should be an implementation detail.
records.extend(res) | ||
return records | ||
|
||
return await self.block_store.get_block_records_by_hash(hashes) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This function now deals with the batching on its own.
for row in await cursor.fetchall(): | ||
block_rec = BlockRecord.from_bytes(row[1]) | ||
all_blocks[block_rec.header_hash] = block_rec | ||
for batch in to_batches(header_hashes, self.db_wrapper.host_parameter_limit): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The batching is now done here.
chia/full_node/full_node_api.py
Outdated
|
||
if request.end_height < request.start_height or request.end_height - request.start_height > 128: | ||
return make_msg(ProtocolMessageTypes.reject_block_headers, reject) | ||
if self.full_node.block_store.db_wrapper.db_version == 2: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We now always try the new get_block_bytes_in_range
API and only fall back to the version 1 api if we get a NotImplementedError
exception
depends_on = [ | ||
"chia.types", | ||
"chia.util", | ||
{ path = "chia.full_node", deprecated = false }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The chia.consensus
module no longer depends upon chia.full_node
. Hooray!
ba1f6d6
to
52c49e1
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR introduces a BlockStoreProtocol
to remove the dependency of chia.consensus
on chia.full_node
, allowing for better separation of concerns and cleaner architecture.
- Creates a new protocol interface
BlockStoreProtocol
that defines the required block store methods - Refactors the blockchain module to use the protocol instead of the concrete
BlockStore
class - Updates block store implementation to include optimized database query batching and improved error handling
Reviewed Changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 1 comment.
Show a summary per file
File | Description |
---|---|
tach.toml | Removes the deprecated dependency from chia.consensus to chia.full_node |
chia/consensus/block_store_protocol.py | Introduces the new BlockStoreProtocol interface |
chia/consensus/blockchain.py | Updates to use BlockStoreProtocol and removes batching logic |
chia/full_node/block_store.py | Implements protocol methods and adds database query optimizations |
chia/full_node/full_node_api.py | Improves exception handling for block store operations |
chia/simulator/full_node_simulator.py | Updates to use the new transaction method |
chia/_tests/blockchain/test_blockchain.py | Removes batch_size parameter from test |
chia/_tests/blockchain/blockchain_test_utils.py | Adds type casting for concrete BlockStore usage |
Comments suppressed due to low confidence (1)
chia/full_node/full_node_api.py
Outdated
blocks_bytes = await self.full_node.block_store.get_block_bytes_in_range( | ||
request.start_height, request.end_height | ||
) | ||
except NotImplementedError: |
Copilot
AI
Jul 18, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The exception handling structure has a logical issue. The except NotImplementedError
block contains code that can raise ValueError
, but the except ValueError
handler is placed after this block, making it unreachable for ValueErrors raised within the NotImplementedError handler.
Copilot uses AI. Check for mistakes.
8372d0d
to
e09d082
Compare
e09d082
to
8047450
Compare
self, blocks_n: int | ||
) -> tuple[dict[bytes32, BlockRecord], Optional[bytes32]]: ... | ||
async def get_prev_hash(self, header_hash: bytes32) -> bytes32: ... | ||
def transaction(self) -> AbstractAsyncContextManager[Any]: ... |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is there a good reason to use Any
instead of aiosqlite.Connection
as the type parameter here?
Doing so would preserve some type safety, and make it easier to refactor (with confidence) into a generic type in the future, when such type exists.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, right now, the only concrete implementation of this protocol returns a aiosqlite.Connection
, but it's not actually used anywhere except in tests. I think full_node
rather just goes to the DBWrapper2
to start a transaction. So yeah, this is pretty useless and dumb which is why I think my next step should really be a ConsensusStoreProtocol
to abstract away the DBWrapper2
/sqlite3
stuff. Mostly I just didn't want consensus to have to depend on aiosqlite
since it's just a detail.
I think ConsensusStore
should actually have an async context manager that returns a thing that you actually do the writes to, and that can have a non-trivial type (although it will be a protocol too).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree it's dumb. The main goal of this PR is to remove chia.full_node
as a dep of chia.consensus
. Once that's done, I can do some better refactoring of storage stuff around a new ConsensusStoreProtocol
. Because of the layer violation that is DBWrapper2
(which forces all stores to be in the same sqlite3 DB), it's kind of hard to type this in a useful way.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it almost seems that those things ought to happen in the opposite order. to avoid Any
and cast()
as a temporary measure in between
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I want to remove the circular dependencies between chia.consensus
and chia.full_node
first so I can be assured that chia.full_node
can't interfere with changes to the chia.consensus
interface.
8047450
to
b92bedf
Compare
This reverts commit f077801.
b92bedf
to
2c71a1f
Compare
This PR has been flagged as stale due to no activity for over 60 days. It will not be automatically closed, but it has been given a stale-pr label and should be manually reviewed by the relevant parties. |
Remove a dependency of chia.full_node from chia.consensus.